import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class HalfDuplexModel {
	Network my_network;

	public static void main(String[] arg) {
		List<Node> users = new ArrayList<Node>();
		List<Edge> channels = new ArrayList<Edge>();

		Node b = new Node(0, "BS");
		b.isTransmiting = true;
		b.received = 3;
		Node s1 = new Node(2.3, "S1");
		Node s2 = new Node(0.7, "S2");

		Edge e1 = new Edge(1, 1, b, s1);
		Edge e2 = new Edge(4, 1, b, s1);
		Edge e3 = new Edge(2, 1, b, s2);
		Edge e4 = new Edge(3, 1, s2, s1);

		users.add(b);
		users.add(s1);
		users.add(s2);

		channels.add(e1);
		channels.add(e2);
		channels.add(e3);
		channels.add(e4);

		Network n = new Network(users, channels);
		HalfDuplexModel hm = new HalfDuplexModel();
		hm.my_network = n;
		long min_time = Long.MAX_VALUE;
		long max_time = 0;
		long total_time = 0;
		
		for (int i = 0; i < 100; i++) {
			long timer = System.nanoTime();
			hm.maxThroughput();
			timer = System.nanoTime()-timer;
			total_time += timer;
			min_time = Math.min(min_time, timer);
			max_time = Math.max(max_time, timer);
			hm.my_network.reset();
			b.isTransmiting = true;
		}
		System.out.println("Throughput: "+hm.maxThroughput());
		System.out.println("Minimum Running Time: "+min_time);
		System.out.println("Maximum Running Time: "+max_time);
		System.out.println("Average Running Time: "+ (total_time/100));

	}

	private void optimumDistribution() {
		Map<Integer, Edge> max = my_network.getEdgeByChannels();
		Edge channel;
		for (int e : max.keySet()) {
			channel = max.get(e);
			channel.my_u.my_out.add(channel);
			channel.my_v.my_in.add(channel);

		}
	}

	public double maxThroughput() {
		double t = 0;
		while (t < 1) {
			optimumDistribution();
			double i = minTransmition(this.nextInterval(t));
			t += i;
			this.transmitFlow(i);
		}
		return this.my_network.getTotalThroughput();
	}

	private void transmitFlow(double f) {
		for (Node n : this.my_network.my_nodes) {
			for (Edge e : n.my_out) {
				e.my_flow = f;
				e.my_v.receive(e);
			}
			n.my_out.clear();
		}
	}

	private double minTransmition(double flow) {
		double min = flow;
		for (Node n : this.my_network.my_nodes) {
			for (Edge e : n.my_out) {
				if (e.my_v.throughput == e.my_v.my_demand) {
					min = flow / 2;
				}
			}
		}
		return min;
	}

	private double nextInterval(double curr_time) {
		double interval = 3;
		for (Node n : this.my_network.my_nodes) {
			if (n.throughput < n.my_demand) {
				interval = Math.min(interval,
						(n.my_demand - n.throughput) / n.incomingValue());
			}
			interval = Math.min(interval, 1 - curr_time);
		}
		return interval;
	}
}
